package com.laolang.pluto.framework.aspect;

import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.servlet.ServletUtil;
import cn.hutool.json.JSONUtil;
import com.laolang.pluto.common.annotation.Log;
import com.laolang.pluto.common.consts.StatusCodeConst;
import com.laolang.pluto.common.exception.BusinessException;
import com.laolang.pluto.common.util.LogUtil;
import com.laolang.pluto.common.util.ServletKit;
import com.laolang.pluto.module.sys.entity.SysOperLog;
import com.laolang.pluto.module.sys.service.SysOperLogService;
import java.lang.reflect.Method;
import java.time.LocalDateTime;
import java.util.Collection;
import java.util.Iterator;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import org.springframework.validation.BindingResult;
import org.springframework.web.multipart.MultipartFile;

/**
 * @author laolang
 * @date 2021-08-05 06:58
 */
@Slf4j
@RequiredArgsConstructor
@Aspect
@Component
public class LogAspect {

    private final SysOperLogService sysOperLogService;

    @Pointcut("@annotation(com.laolang.pluto.common.annotation.Log)")
    public void poincut() {

    }

    /**
     * 执行请求后
     */
    @AfterReturning(pointcut = "poincut()", returning = "r")
    public void afterReturning(JoinPoint joinPoint, Object r) {
        handleLog(joinPoint, null, r);
    }

    /**
     * 抛出异常后
     */
    @AfterThrowing(pointcut = "poincut()", throwing = "e")
    public void afterThowing(JoinPoint joinPoint, Exception e) {
        handleLog(joinPoint, e, null);
    }

    private void handleLog(final JoinPoint joinPoint, final Exception e, Object r) {
        boolean flag = false;
        BusinessException be = null;
        try {
            Log controllerLog = getAnnotationLog(joinPoint);
            if (null == controllerLog) {
                return;
            }
            // 客户端 ip
            String clientIP = ServletUtil.getClientIP(ServletKit.getRequest());
            // json 相应
            String jsonResult = JSONUtil.toJsonStr(r);
            String className = joinPoint.getTarget().getClass().getName();
            String methodName = joinPoint.getSignature().getName();
            String method = className + "." + methodName;
            String requestMethod = ServletKit.getRequest().getMethod();
            String moduleName = controllerLog.moduleName();
            Integer businesType = controllerLog.businessType().getCode();
            Integer channel = controllerLog.channel().getCode();

            SysOperLog sysOperLog = SysOperLog.builder()
                    .operIp(clientIP)
                    .requestMethod(requestMethod)
                    .method(method)
                    .responseResult(jsonResult)
                    .moduleName(moduleName)
                    .businessType(businesType)
                    .operatorChannel(channel)
                    .operTime(LocalDateTime.now())
                    .build();
            if (controllerLog.saveRequestParam()) {
                setRequestValue(joinPoint, sysOperLog);
            }

            if (null != e) {
                if (e instanceof BusinessException) {
                    be = (BusinessException) e;
                } else {
                    be = new BusinessException(StatusCodeConst.ERROR);
                }
                sysOperLog.setBizCode(be.getCode());
                sysOperLog.setErrorMsg(be.getMsg());

            }

            // TODO 异步保存日志信息
            sysOperLogService.save(sysOperLog);
            flag = true;
            if (null != be) {
                throw be;
            }
        } catch (Exception ex) {
            if (flag) {
                if (null != be) {
                    LogUtil.error(log, StrUtil.format("业务异常:{}:{}", be.getCode(), be.getMsg()));
                    throw be;
                }
            }
            LogUtil.error(log, "前置通知异常");
            LogUtil.error(log, StrUtil.format("异常信息:{}", ExceptionUtils.getStackTrace(ex)));
        }
    }

    // TODO 优化请求参数设置
    private void setRequestValue(JoinPoint joinPoint, SysOperLog sysOperLog) {
        String params = argsArrayToString(joinPoint.getArgs());
        sysOperLog.setRequestParam(StrUtil.sub(params, 0, 2000));

        //String method = sysOperLog.getMethod();
        //if (HttpMethod.PUT.name().equals(method) || HttpMethod.POST.name().equals(method)) {
        //    String params = argsArrayToString(joinPoint.getArgs());
        //    sysOperLog.setRequestParam(StrUtil.sub(params, 0, 2000));
        //} else {
        //    Map<?, ?> paramsMap =
        //            (Map<?, ?>) ServletKit.getRequest().getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE);
        //    String s = paramsMap.toString();
        //    if( StrUtil.isBlank(s)){
        //        s = argsArrayToString(joinPoint.getArgs());
        //    }
        //    sysOperLog.setRequestParam(StrUtil.sub(s, 0, 2000));
        //}
    }

    /**
     * 参数拼装
     */
    private String argsArrayToString(Object[] paramsArray) {
        String params = "";
        if (paramsArray != null && paramsArray.length > 0) {
            for (int i = 0; i < paramsArray.length; i++) {
                if (null != paramsArray[i] && !isFilterObject(paramsArray[i])) {
                    Object jsonObj = JSONUtil.toJsonStr(paramsArray[i]);
                    params += jsonObj.toString() + " ";
                }
            }
        }
        return params.trim();
    }

    /**
     * 判断是否需要过滤的对象。
     *
     * @param o 对象信息。
     * @return 如果是需要过滤的对象，则返回true；否则返回false。
     */
    @SuppressWarnings("rawtypes")
    public boolean isFilterObject(final Object o) {
        Class<?> clazz = o.getClass();
        if (clazz.isArray()) {
            return clazz.getComponentType().isAssignableFrom(MultipartFile.class);
        } else if (Collection.class.isAssignableFrom(clazz)) {
            Collection collection = (Collection) o;
            for (Iterator iter = collection.iterator(); iter.hasNext(); ) {
                return iter.next() instanceof MultipartFile;
            }
        } else if (Map.class.isAssignableFrom(clazz)) {
            Map map = (Map) o;
            for (Iterator iter = map.entrySet().iterator(); iter.hasNext(); ) {
                Map.Entry entry = (Map.Entry) iter.next();
                return entry.getValue() instanceof MultipartFile;
            }
        }
        return o instanceof MultipartFile || o instanceof HttpServletRequest || o instanceof HttpServletResponse
                || o instanceof BindingResult;
    }

    /**
     * 获取 @{link Log} 注解
     */
    private Log getAnnotationLog(JoinPoint joinPoint) {
        Signature signature = joinPoint.getSignature();
        MethodSignature methodSignature = (MethodSignature) signature;
        Method method = methodSignature.getMethod();
        if (null == method) {
            return null;
        }
        return method.getAnnotation(Log.class);
    }

}
